home *** CD-ROM | disk | FTP | other *** search
/ Commodore Free 31 / Commodore_Free_Issue_31_2009_Commodore_Computer_Club.d64 / c part 2.2 < prev    next >
Text File  |  2023-02-26  |  17KB  |  666 lines

  1. u
  2.  
  3. Alternative Programming Languages: C
  4.              Part 2
  5.          By Paul Davis
  6.  
  7.      CONTINUED FROM PART 2.1
  8.  
  9. This program still does nothing. I
  10. just want to demonstrate what happens
  11. when a change is made to a file. Save
  12. the file and enter the make command:
  13.  
  14.   make
  15.  
  16. The make command will run through all
  17. the dependencies and determine that
  18. the '.c' source file has changed so
  19. it needs to run the cc65 command to
  20. compile it. Then, since the '.s'
  21. assembler source has now changed, it
  22. needs to run the ca65 command to
  23. assemble it. Then, since the '.o'
  24. object file has now changed it needs
  25. to run the ld65 linker to create the
  26. executable. The makefile has helped
  27. us to remove some of the complexity
  28. of using the separate compiler tools.
  29. Now, whenever you make a change to
  30. your program, just issue the 'make'
  31. command and it is automatically
  32. compiled and linked.
  33.  
  34. Makefiles have many other features
  35. that can simplify the compilation
  36. process even further. These features
  37. are beyond the scope of this article.
  38. I will leave it as an exercise for
  39. the reader to investigate this in
  40. more detail.
  41.  
  42. Creating the function library
  43.  
  44. Finally, we're armed with the
  45. knowledge and tools required to
  46. create our sprite handling function
  47. library. So let's get started. First,
  48. we should start from the point of
  49. having a working program that we will
  50. gradually change to use our library
  51. functions. Edit the 'sprtst.c' file
  52. and change it to the following:
  53.  
  54. #include <string.h>
  55. #include <peekpoke.h>
  56. #include <c64.h>
  57. #include "mytypes.h"
  58.  
  59. #define SPRITE0_DATA 0x0340
  60. #define SPRITE0_PTR 0x07F8
  61.  
  62. void main(void)
  63.  
  64.  
  65. memset((void*)SPRITE0_DATA,0xff,64);
  66.   POKE(SPRITE0_PTR, SPRITE0_DATA/64);
  67.   VIC.spr0_colour = COLOR_WHITE;
  68.   VIC.spr_hi_x = 0;
  69.   VIC.spr0_x = 100;
  70.   VIC.spr0_y = 100;
  71.   VIC.spr_ena = 1;
  72.  
  73.  
  74. Compile it (using the 'make' command)
  75. and run the 'sprtst' program in VICE
  76. to check that it works.
  77.  
  78. Now we have a working program, we can
  79. write and test functions in our
  80. library one at a time by replacing
  81. the lines in this program with calls
  82. to the new function. This incremental
  83. approach: write a bit, test a bit, is
  84. a good way to build a library,
  85. especially when you're new to this
  86. kind of development.
  87.  
  88. It's always a good idea to start with
  89. a little planning. We need to decide
  90. what features we want the library to
  91. provide for us. It's easy to get
  92. carried away and put anything and
  93. everything in there. Some thought at
  94. the beginning can help to focus our
  95. efforts.
  96.  
  97. For our example library we will keep
  98. things simple. We want to be able to
  99. show and hide sprites, change their
  100. colour and move them anywhere on the
  101. screen. We also want to be able to
  102. use all 8 sprites, not just one. This
  103. last requirement gives us a clue that
  104. many of the library functions will
  105. need to take a sprite number as a
  106. parameter.
  107.  
  108. Okay, let's start with one of the
  109. easier functions, the one that
  110. enables a particular sprite and shows
  111. it on the screen. In our test program
  112. above we simply put the number 1 into
  113. VIC.spr_ena, but our requirement to
  114. use all the sprites means we need to
  115. do more than this.
  116.  
  117. The 'sprite enable' register of the
  118. VIC chip is actually a bit-mapping.
  119. In other words, each bit of the
  120. number stored there corresponds to
  121. the setting for one sprite. Since
  122. there are 8 sprites and 8 bits in a
  123. byte, this fits nicely. The question
  124. is, how do we get to a particular
  125. bit?
  126.  
  127. The C language has a convenient way
  128. of doing this using the 'shift left'
  129. operator. The expression '1 << n'
  130. where 'n' is the sprite number will
  131. generate the correct value. It's
  132. beyond the scope of this article to
  133. explain in detail why this is, so for
  134. now, just take it on faith that it
  135. works.
  136.  
  137. With this knowledge, we can now make
  138. an attempt at a function to enable
  139. any sprite. Create a new file called
  140. 'sprlib.c' and enter this code into
  141. it:
  142.  
  143. #include <c64.h>
  144. #include "sprlib.h"
  145.  
  146. void spr_show(byte spr)
  147.  
  148.   VIC.spr_ena = 1 << spr;
  149.  
  150.  
  151. We have called the function spr_show.
  152. It's usually a good idea to prefix
  153. the names of functions in a library
  154. with the same few characters to
  155. identify which library they belong to
  156. and avoid name collisions with
  157. functions in other libraries. Here we
  158. are using 'spr' for our sprite
  159. library.
  160.  
  161. The function takes a sprite number as
  162. a parameter and uses the expression
  163. '1 << spr' to calculate the bit value
  164. for that sprite. It then uses the
  165. '=' operator to combine this bit
  166. value with the value that is already
  167. in the register. If we just assigned
  168. the value with '=' it would turn off
  169. all the other sprites except the one
  170. we want to show. The '' symbol is
  171. the 'bitwise OR' operator in C.
  172.  
  173. Next, we need to provide a way for
  174. other programs to call our function.
  175. We do this by creating a header file
  176. for those programs to include. Create
  177. a new file called 'sprlib.h' and
  178. enter these lines:
  179.  
  180. #include <c64.h>
  181. #include "mytypes.h"
  182.  
  183. void spr_show(byte spr);
  184.  
  185. The declaration of the spr_show
  186. function here is called a 'function
  187. prototype'. It just lists the return
  188. type, name and parameters of the
  189. function. Notice that the prototype
  190. doesn't have any code after it in
  191. curly braces and that the line ends
  192. with a semi-colon. Prototypes are C's
  193. way of telling other programs that a
  194. function exists and what its return
  195. type and parameters are.
  196.  
  197. Next, in the 'sprtst.c' file, change
  198. the #include "mytypes.h" line to
  199. read:
  200.  
  201. #include "sprlib.h"
  202.  
  203. and change the line that reads:
  204.  
  205.   VIC.spr_ena = 1;
  206.  
  207. to use our new function instead:
  208.  
  209.   spr_show(0);
  210.  
  211. Now we need to change our 'makefile'
  212. to add the instructions to compile
  213. the library. Change 'makefile' to
  214. look like the text below. Remember to
  215. indent the commands with a tab, not
  216. spaces (the ld65 command should be
  217. entered all on one line, even though
  218. it has wrapped around in the magazine
  219. listing):
  220.  
  221. sprtst: sprtst.o spr.lib
  222.     ld65 -t c64 -o sprtst c64.o
  223. sprtst.o spr.lib c64.lib
  224.  
  225. sprtst.o: sprtst.s
  226.     ca65 -t c64 sprtst.s
  227.  
  228. sprtst.s: sprtst.c sprlib.h
  229.     cc65 -t c64 sprtst.c
  230.  
  231. spr.lib: sprlib.o
  232.     ar65 a spr.lib sprlib.o
  233.  
  234. sprlib.o: sprlib.s
  235.     ca65 -t c64 sprlib.s
  236.  
  237. sprlib.s: sprlib.c sprlib.h mytypes.h
  238.     cc65 -t c64 sprlib.c
  239.  
  240. This new makefile contains
  241. instructions to compile the sprlib.c
  242. file and contains a new command
  243. 'ar65' that puts object files into a
  244. library file. Our sprite library is
  245. called 'spr.lib'. The entry for
  246. 'sprtst' has also been changed to add
  247. spr.lib as a dependency and as part
  248. of the 'ld65' link instruction.
  249.  
  250. Compile these changes by running the
  251. 'make' command. If all goes well, it
  252. should compile sprlib, create the
  253. library and compile sprtst, linking
  254. with the new library. Load and run
  255. this program in VICE to check it
  256. still works.
  257.  
  258. Next, we will create the function to
  259. change the sprite colour. This will
  260. need to take two parameters, the
  261. sprite number and the colour value.
  262. Add the following lines to the end of
  263. 'sprlib.c':
  264.  
  265. void spr_colour(byte spr, byte
  266. colour)
  267.  
  268.   (&VIC.spr0_colour)[spr] = colour;
  269.  
  270.  
  271. The use of the ampersand in this
  272. function needs some explanation. The
  273. VIC chip registers for sprite colours
  274. are conveniently located together in
  275. memory. The colour for sprite 1
  276. immediately follows that for sprite
  277. 0. We can take advantage of this
  278. arrangement and treat those registers
  279. like an array of bytes. The only
  280. problem is the variable
  281. VIC.spr0_colour is of type 'byte' so
  282. we can't treat it directly as an
  283. array.
  284.  
  285. In C, an 'array' type is nothing more
  286. than a pointer to the memory address
  287. of the first element. It follows
  288. therefore, that if we can get the
  289. memory address of the VIC.spr0_colour
  290. variable then we can treat it as an
  291. array. This is exactly what the
  292. ampersand operator is used for here.
  293. An ampersand returns the memory
  294. address of the variable name that
  295. follows it. Once we have that
  296. address, we can simply use the
  297. familiar square brackets as an index
  298. into this 'array'.
  299.  
  300. As before, we need to add a function
  301. prototype to the end of 'sprlib.h':
  302.  
  303. void spr_colour(byte spr,
  304.                 byte colour);
  305.  
  306. In the test program, 'sprtst.c', edit
  307. the line that changes the sprite
  308. colour to use the new function:
  309.  
  310. spr_colour(0, COLOR_WHITE);
  311.  
  312. Compile all the changes by issuing
  313. the 'make' command and run the
  314. program in VICE to test it still
  315. works.
  316.  
  317. Let's keep up the momentum and create
  318. a function to set a sprite pointer.
  319. Enter this code into 'sprlib.c':
  320.  
  321. byte *sprite_pointers =
  322. (byte*)0x07F8;
  323.  
  324. void spr_ptr(byte spr, byte ptr)
  325.  
  326.   sprite_pointers[spr] = ptr;
  327.  
  328.  
  329. For the moment, we have just
  330. hard-coded the start address of the
  331. sprite pointers in memory. It is
  332. possible, and preferable, to use the
  333. VIC registers to calculate what this
  334. address should be. We will see how to
  335. do this later.
  336.  
  337. Add this prototype to the 'sprlib.h'
  338. file:
  339.  
  340. void spr_ptr(byte spr, byte ptr);
  341.  
  342. Finally, change the line in
  343. 'sprtst.c' that pokes the sprite
  344. pointer to use the new function:
  345.  
  346. spr_ptr(0, SPRITE0_DATA / 64);
  347.  
  348. Compile with 'make' and test in VICE
  349. as usual to check it still works. By
  350. now you should be starting to
  351. appreciate the benefit of the make
  352. tool. The effort put in setting it up
  353. at the start is now being paid back
  354. many times over. Making a change to
  355. the library is very simple and we
  356. have got into a good rhythm of
  357. editing the source files, compiling
  358. and testing.
  359.  
  360. The function to set the sprite
  361. pointer is okay, but it still makes
  362. the caller calculate the pointer
  363. value from the real address. We could
  364. change the function, or write a new
  365. one, that takes an address rather
  366. than a pointer but there are times
  367. when pointer numbers are the better
  368. fit, animation being the prime
  369. example. Our library would be more
  370. useful if it provided a way to
  371. calculate the pointer number from an
  372. address. We could write a function to
  373. do this but C provides another way,
  374. macros. Enter this line into the
  375. 'sprlib.h' header file (all on one
  376. line):
  377.  
  378. #define SPR_PTR(addr)
  379.                (((addr) / 64) & 0xff)
  380.  
  381. In the 'sprtst.c' file change the
  382. line that sets the pointer to this:
  383.  
  384. spr_ptr(0, SPR_PTR(SPRITE0_DATA));
  385.  
  386. We have seen the #define instruction
  387. used to create constants before. Here
  388. it is being used to create a macro.
  389. To the program using it, a macro
  390. looks just like a function. But
  391. unlike a function, it is not called.
  392. Instead, the macro is literally
  393. replaced by its text value. To
  394. demonstrate, let's see what the
  395. compiler would do with the spr_ptr
  396. call above.
  397.  
  398. The compiler recognises
  399. SPR_PTR(SPRITE0_DATA) as a macro and
  400. expands it using the text in the
  401. original definition. The resulting
  402. instruction would look like this:
  403.  
  404. spr_ptr(0,(((SPRITE0_DATA)/64)&0xff));
  405.  
  406.  
  407. The value SPRITE0_DATA is also a
  408. defined constant so this too will be
  409. replaced with its literal value. The
  410. command now looks like this:
  411.  
  412. spr_ptr(0,(((0x0340)/64) & 0xff));
  413.  
  414. The compiler is clever enough to
  415. realise that because this expression
  416. uses literal values it can simplify
  417. it by calculating the result. The
  418. command now looks like this:
  419.  
  420. spr_ptr(0, 13);
  421.  
  422. This is the major benefit of using
  423. macros instead of functions for some
  424. tasks. They can help to optimise the
  425. code, especially when used with
  426. constant expressions and values.
  427.  
  428. The last function that we need to
  429. write to finish our test program
  430. moves a sprite to any position on the
  431. screen. Enter this function code into
  432. 'sprlib.c':
  433.  
  434. void spr_move(byte spr, int x, int y)
  435.  
  436.   byte index = 2 * spr;
  437.   (&VIC.spr0_x)[index] = x & 0xff;
  438.   (&VIC.spr0_y)[index] = y;
  439.   if (x & 0x100)
  440.     VIC.spr_hi_x = 1 << spr;
  441.   else VIC.spr_hi_x &= (1 << spr);
  442.  
  443.  
  444. This new function is a bit more
  445. complicated than those we have
  446. previously written, but it uses
  447. several of the same ideas. Part of
  448. this complexity is caused by the
  449. layout of the sprite position
  450. registers in memory. Unlike the
  451. colour registers which are
  452. conveniently grouped together, the
  453. position registers are grouped in
  454. pairs: sprite0_x, sprite0_y,
  455. sprite1_x, sprite1_y and so on. This
  456. means that we can't use the sprite
  457. number directly as an index to an
  458. 'array' of registers. The function
  459. needs to multiply the sprite number
  460. by 2 to get the array index because
  461. there are groups of two registers per
  462. sprite.
  463.  
  464. The spr_move function has also
  465. introduced a couple of new operators.
  466. The tilde character '' is the
  467. bitwise NOT operator in C and the
  468. ampersand is the bitwise AND
  469. operator. Together they are used to
  470. turn off a particular bit in a value.
  471. At some point in the future I'm
  472. hoping to write an article that
  473. describes in more detail how these
  474. 'bit manipulations' work. For the
  475. purposes of this article however,
  476. suffice it to say that to turn a bit
  477. on, use an expression like this:
  478.  
  479.   variable = 1 << bit
  480.  
  481. And to turn a bit off, use an
  482. expression like this:
  483.  
  484.   variable &= (1 << bit)
  485.  
  486. We now need to add the prototype for
  487. this function into 'sprlib.h':
  488.  
  489. void spr_move(byte spr, int x, int
  490. y);
  491.  
  492. And replace these lines in
  493. 'sprtst.c':
  494.  
  495.   VIC.spr_hi_x = 0;
  496.   VIC.spr0_x = 100;
  497.   VIC.spr0_y = 100;
  498.  
  499. with a call to the new function:
  500.  
  501. spr_move(0, 100, 100);
  502.  
  503. Compile these changes with 'make' and
  504. test the program in VICE as usual.
  505.  
  506. At this point we should take the
  507. opportunity to tidy up the test
  508. program. There are a couple of
  509. include files and the definition of
  510. the sprite pointer address that are
  511. no longer needed. Remove these lines
  512. from 'sprtst.c':
  513.  
  514. #include <peekpoke.h>
  515. #include <c64.h>
  516.  
  517. #define SPRITE0_PTR 0x07F8
  518.  
  519. Compile again with 'make' to check it
  520. still compiles.
  521.  
  522. To finish, let's add a couple of new
  523. functions. One will calculate the
  524. address of the sprite pointers using
  525. the VIC registers instead of
  526. hard-coding the value into the
  527. library. The other will allow a
  528. sprite to be expanded. Enter this
  529. code into 'sprlib.c':
  530.  
  531. void spr_init(void)
  532.  
  533.   sprite_pointers = (byte*)
  534.     ((((CIA2.pra & 3) ^ 3) * 0x4000)
  535.      ((VIC.addr & 0xf0) * 0x40)
  536.     + 0x03f8);
  537.  
  538.  
  539. void spr_expand(byte spr,
  540. bool expandx, bool expandy)
  541.  
  542.   if (expandx)
  543.     VIC.spr_exp_x = 1 << spr;
  544.   else VIC.spr_exp_x &= (1 << spr);
  545.  
  546.   if (expandy)
  547.     VIC.spr_exp_y = 1 << spr;
  548.   else VIC.spr_exp_y &= (1 << spr);
  549.  
  550.  
  551. The expression in the spr_init
  552. function isn't quite as scary as it
  553. looks. It's basically split into two
  554. parts, the first half works out which
  555. VIC bank of memory is being used and
  556. multiplies this by 0x4000 to get the
  557. base memory address for that bank.
  558. The second half of the expression
  559. reads the VIC register that controls
  560. where in memory the text screen is
  561. located and uses that to calculate
  562. where the sprites are within the VIC
  563. bank. Combining the two halves of the
  564. expression gives the actual memory
  565. address of the sprite pointers.
  566.  
  567. Next, enter the function prototypes
  568. into 'sprlib.h':
  569.  
  570. void spr_init(void);
  571. void spr_expand(byte spr,
  572.          bool expandx, bool expandy);
  573.  
  574. Now, let's do something a bit more
  575. with our new functions and show three
  576. sprites. Change 'sprtst.c' to look
  577. like this:
  578.  
  579. #include <string.h>
  580. #include "sprlib.h"
  581.  
  582. #define SPR_DATA 0x0340
  583. #define SPR0_DATA (SPR_DATA+0x00)
  584. #define SPR1_DATA (SPR_DATA+0x40)
  585. #define SPR2_DATA (SPR_DATA+0x80)
  586.  
  587. void main(void)
  588.  
  589.   memset((void*)SPR0_DATA, 0xff, 64);
  590.   memset((void*)SPR2_DATA, 0xcc, 64);
  591.   memset((void*)SPR1_DATA, 0xaa, 64);
  592.  
  593.   spr_init();
  594.   spr_ptr(0, SPR_PTR(SPR0_DATA));
  595.   spr_ptr(1, SPR_PTR(SPR1_DATA));
  596.   spr_ptr(2, SPR_PTR(SPR2_DATA));
  597.   spr_colour(0, COLOR_YELLOW);
  598.   spr_colour(1, COLOR_GREEN);
  599.   spr_colour(2, COLOR_BLACK);
  600.   spr_move(0, 290, 100);
  601.   spr_move(1, 280, 110);
  602.   spr_move(2, 270, 120);
  603.   spr_expand(0, TRUE, FALSE);
  604.   spr_expand(1, FALSE, TRUE);
  605.   spr_expand(2, TRUE, TRUE);
  606.   spr_show(0);
  607.   spr_show(1);
  608.   spr_show(2);
  609.  
  610.  
  611. Compile the program with 'make' and
  612. run it in VICE to see the results.
  613.  
  614. Over to you
  615.  
  616. In the limited space of this article
  617. we haven't been able to create a
  618. complete library. There are several
  619. other functions that would be useful
  620. additions. I will leave this as an
  621. exercise for the reader to implement
  622. some of these missing functions.
  623.  
  624. To get you started with some ideas,
  625. how about writing a function to hide
  626. a particular sprite and one to hide
  627. all sprites? You could also try
  628. writing a function to set the
  629. multicolour mode of a sprite and
  630. functions to set the corresponding
  631. multicolour registers.
  632.  
  633. An implementation of these functions,
  634. plus a few more, can be found in the
  635. zip file that accompanies this
  636. article on my website, listed below.
  637.  
  638. Final words
  639.  
  640. Last time, I mentioned that this
  641. article would show how to implement a
  642. function in assembly language.
  643. Unfortunately, there wasn't enough
  644. space to include that content here.
  645. If there is enough demand I will
  646. write a third part to this series
  647. focussing on optimisation techniques
  648. and assembly language integration.
  649. Please write in to the magazine or
  650. post a comment on my blog if you
  651. would be interested in reading about
  652. this.
  653.  
  654. All my articles and their associated
  655. resources are now available at my web
  656. site which can be found at
  657. http://sites.google.com/site/
  658.  develocity/commodore/articles.
  659.  
  660. Or leave a comment on any of the
  661. articles at my blog which is at
  662. http://retrodev.blogspot.com/
  663.  
  664. --
  665.  
  666.